﻿namespace Sai.Console
{
	using System.Collections.Generic;
	using System.IO;
	using System.Text;
	using Sai.Core;
	using Sai.Core.Reporting;

	/// <summary>
	/// Represents command line options supplied by the user.
	/// </summary>
	internal class CommandLineOptions
	{
        public CommandLineOptions()
        {
            this.ReportOnAdditions = true;
            this.ReportOnChanges = true;
            this.ReportOnObsolete = true;
            this.ReportOnRemovals = true;
        }

		/// <summary>
		/// Metadata value used in help.
		/// </summary>
		private const string ProjectMetaValue = "<project file>";

		/// <summary>
		/// Switch to specify product name.
		/// </summary>
		private const string NameSwitch = "/name";

		/// <summary>
		/// Metadata for name switch used in help.
		/// </summary>
		private const string NameMetaValue = "<name>";

		/// <summary>
		/// Description of the name used in help.
		/// </summary>
		private const string NameSwitchDescription = "the name of this project";
		
		/// <summary>
		/// Friendly name for the from (earlier) version of the software.
		/// </summary>
		private const string FromVersionSwitch = "/from";

		/// <summary>
		/// Description of the from switch.
		/// </summary>
		private const string FromVersionSwitchDescription = "the name of the previous version of the API";

		/// <summary>
		/// The folder containing the earlier version of the software.
		/// </summary>
		private const string FromVersionFolderSwitch = "/from-folder";

		/// <summary>
		/// Description of from folder.
		/// </summary>
		private const string FromVersionFolderSwitchDescription = "the folder containing the previous version of the API";

		/// <summary>
		/// Friendly name for newer version of the software.
		/// </summary>
		private const string ToVersionSwitch = "/to";

		/// <summary>
		/// Description of the to version option.
		/// </summary>
		private const string ToVersionSwitchDescription = "the name of the new version of the API";

		/// <summary>
		/// The folder containing the newer version of the software.
		/// </summary>
		private const string ToVersionFolderSwitch = "/to-folder";

		/// <summary>
		/// Description of the newer folder option.
		/// </summary>
		private const string ToVersionFolderSwitchDescription = "the folder containing the new version of the API";

		/// <summary>
		/// Folder where reports are generated.
		/// </summary>
		private const string OutputFolderSwitch = "/out";

		/// <summary>
		/// Description for output folder option.
		/// </summary>
		private const string OutputFolderSwitchDescription = "where to place the report output";

		/// <summary>
		/// Reduce verbosity in console output.
		/// </summary>
		private const string QuietSwitch = "/quiet";

		/// <summary>
		/// Description for quiet option.
		/// </summary>
		private const string QuietSwitchDescription = "do not output console messages";

		/// <summary>
		/// Clean report output prior to generating reports.
		/// </summary>
		private const string CleanSwitch = "/clean";

		/// <summary>
		/// Description of clean option.
		/// </summary>
		private const string CleanSwitchDescription = "remove existing files from output folder";

		/// <summary>
		/// Change output format to plain xml.
		/// </summary>
		private const string XmlSwitch = "/xml";

		/// <summary>
		/// Description of xml option.
		/// </summary>
		private const string XmlSwitchDescription = "output reports in xml format";

		/// <summary>
		/// Change output format to HTML (the default).
		/// </summary>
		private const string HtmlSwitch = "/html";

		/// <summary>
		/// Description of html option.
		/// </summary>
		private const string HtmlSwitchDescription = "output reports in html format (default)";

        /// <summary>
        /// Change output format to legacy HTML.
        /// </summary>
        private const string LegacyHtmlSwitch = "/html4";

        /// <summary>
        /// Description of legacy html option.
        /// </summary>
        private const string LegacyHtmlSwitchDescription = "output reports in html 4 format";

        /// <summary>
        /// Change output format to Markdown.
        /// </summary>
        private const string MarkdownSwitch = "/md";

        /// <summary>
        /// Description of Markdown option.
        /// </summary>
        private const string MarkdownSwitchDescription = "output reports in markdown format";

		/// <summary>
		/// Change output format to MAML (for sandcastle)
		/// </summary>
		private const string MamlSwitch = "/maml";

		/// <summary>
		/// Description for maml option.
		/// </summary>
		private const string MamlSwitchDescription = "output reports in maml format";

		/// <summary>
		/// Path to sandcastle project file.
		/// </summary>
		private const string SandcastleSwitch = "/shfb";

		/// <summary>
		/// Description for sandcastle project option.
		/// </summary>
		private const string SandcastleSwitchDescription = "sandcastle help file builder project";

		/// <summary>
		/// Guid of topic id to update with content.
		/// </summary>
		private const string TopicIdSwitch = "/topic";

		/// <summary>
		/// Description for topic id option.
		/// </summary>
		private const string TopicIdSwitchDescription = "sandcastle topic id";

        /// <summary>
        /// Turn off reporting on additions.
        /// </summary>
        private const string AdditionsOff = "-a";

        /// <summary>
        /// Turn on reporting on additions.
        /// </summary>
        private const string AdditionsOn = "+a";

        /// <summary>
        /// Turn off reporting on changes.
        /// </summary>
        private const string ChangesOff = "-c";

        /// <summary>
        /// Turn on reporting on changes.
        /// </summary>
        private const string ChangesOn = "+c";

        /// <summary>
        /// Turn off reporting on removals.
        /// </summary>
        private const string RemovalsOff = "-r";

        /// <summary>
        /// Turn on reporting on removals.
        /// </summary>
        private const string RemovalsOn = "+r";

        /// <summary>
        /// Description of reporting options
        /// </summary>
        private const string ReportingOptionsDescription = "Turn on (+) or off(-) [a]dditions, [c]hanges, [r]emovals, [o]bsolete";

        /// <summary>
        /// Turn off reporting on obsolete members.
        /// </summary>
        private const string ObsoleteOff = "-o";

        /// <summary>
        /// Turn on reporting on obsolete members.
        /// </summary>
        private const string ObsoleteOn = "+o";

		/// <summary>
		/// Metadata showing a guid object in the help.
		/// </summary>
		private const string GuidMetaValue = "<guid>";

		/// <summary>
		/// Metadata showing a version object in the help.
		/// </summary>
		private const string VersionMetaValue = "<version>";

		/// <summary>
		/// Metadata showing a folder object in the help.
		/// </summary>
		private const string FolderMetaValue = "<folder>";

		/// <summary>
		/// Metadata showing a file object in the help.
		/// </summary>
		private const string FileMetaValue = "<file>";

		/// <summary>
		/// Friendly name for the product under analysis.
		/// </summary>
		public string ProductName { get; set; }

		/// <summary>
		/// Optional project file used to specify options.
		/// </summary>
		public string ProjectFile { get; set; }

		/// <summary>
		/// A friendly name for the "old" version
		/// </summary>
		public string FromVersion { get; set; }
		
		/// <summary>
		/// The folder containing the "old" version of the assemblies.
		/// </summary>
		public string FromVersionFolder { get; set; }

		/// <summary>
		/// A friendly name for the "new" version
		/// </summary>
		public string ToVersion { get; set; }

		/// <summary>
		/// The folder containing the "new" version of the assemblies.
		/// </summary>
		public string ToVersionFolder { get; set; }

		/// <summary>
		/// Folder where the report files are generated.
		/// </summary>
		public string OutputFolder { get; set; }

		/// <summary>
		/// Option to reduce number of lines of output to console.
		/// </summary>
		public bool Quiet { get; set; }

		/// <summary>
		/// Option to clean output folder before generating new reports.
		/// </summary>
		public bool Clean { get; set; }

        /// <summary>
        /// Option to report on additions to code base.
        /// </summary>
        public bool ReportOnAdditions { get; set; }

        /// <summary>
        /// Option to report on changes to code base.
        /// </summary>
        public bool ReportOnChanges { get; set; }

        /// <summary>
        /// Option to report on removals from code base.
        /// </summary>
        public bool ReportOnRemovals { get; set; }

        /// <summary>
        /// Option to report on obsolete types and members in code base.
        /// </summary>
        public bool ReportOnObsolete { get; set; }

		/// <summary>
		/// Format used for report generation.
		/// </summary>
		public ReportFormat ReportFormat { get; set; }

		/// <summary>
		/// The SHFBProj file associated with help file.
		/// </summary>
		public string SandcastleProject { get; set; }

		/// <summary>
		/// Topic id to replace with generated MAML content.
		/// </summary>
		public string TopicId { get; set; }

		/// <summary>
		/// Explains the command line interface in cases where options are not specified fully or correctly.
		/// </summary>
		/// <param name="applicationName">The name of the application.</param>
		/// <returns>Text describing the usage.</returns>
		public string Usage(string applicationName)
		{
			var builder = new StringBuilder();

			builder.AppendLine();
			builder.Append(applicationName);
			builder.AppendLine(" searches for differences between two versions");
			builder.AppendLine("of a set of .NET assemblies and reports on breaking changes.");
			builder.AppendLine();

			builder.AppendFormat("Syntax: {0} {1} or [options] ", applicationName, ProjectMetaValue);
			builder.AppendLine();
			builder.AppendLine();
			builder.AppendLine("Required Options:");
			builder.AppendLine();

			builder.AppendFormat("{0} {1} : {2}", NameSwitch, NameMetaValue, NameSwitchDescription);
			builder.AppendLine();
			builder.AppendFormat("{0} {1} : {2}", FromVersionSwitch, VersionMetaValue, FromVersionSwitchDescription);
			builder.AppendLine();
			builder.AppendFormat("{0} {1} : {2}", FromVersionFolderSwitch, FolderMetaValue, FromVersionFolderSwitchDescription);
			builder.AppendLine();
			builder.AppendFormat("{0} {1} : {2}", ToVersionSwitch, VersionMetaValue, ToVersionFolderSwitchDescription);
			builder.AppendLine();
			builder.AppendFormat("{0} {1} : {2}", ToVersionFolderSwitch, FolderMetaValue, ToVersionFolderSwitchDescription);
			builder.AppendLine();
			
			builder.AppendLine();

			builder.AppendLine("Optional ...er... Options:");
			builder.AppendLine();

			builder.AppendFormat("{0} {1} : {2}", OutputFolderSwitch, FolderMetaValue, OutputFolderSwitchDescription);
			builder.AppendLine();

			builder.AppendFormat("{0} : {1}", HtmlSwitch, HtmlSwitchDescription);
			builder.AppendLine();

            builder.AppendFormat("{0} : {1}", MarkdownSwitch, MarkdownSwitchDescription);
            builder.AppendLine();

			builder.AppendFormat("{0} : {1}", MamlSwitch, MamlSwitchDescription);
			builder.AppendLine();

			builder.AppendFormat("{0} : {1}", XmlSwitch, XmlSwitchDescription);
			builder.AppendLine();

			builder.AppendFormat("{0} {1} : {2}", SandcastleSwitch, FileMetaValue, SandcastleSwitchDescription);
			builder.AppendLine();

			builder.AppendFormat("{0} {1} : {2}", TopicIdSwitch, GuidMetaValue, TopicIdSwitchDescription);
			builder.AppendLine();

			builder.AppendFormat("{0} : {1}", QuietSwitch, QuietSwitchDescription);
			builder.AppendLine();

			builder.AppendFormat("{0} : {1}", CleanSwitch, CleanSwitchDescription);
			builder.AppendLine();

            builder.AppendFormat("{0}, {1}, {2}, {3} : {4}", AdditionsOn, ChangesOn, RemovalsOn, ObsoleteOn, ReportingOptionsDescription);
            builder.AppendLine();

			builder.AppendLine();

			//builder.AppendLine("   " + ContentLayoutFileSwitch + ":<file>   Optional Content Layout file to update");
			//builder.AppendLine();

			builder.AppendLine("Example:");
			builder.AppendLine();
			builder.AppendLine();
			builder.AppendFormat(applicationName);
			builder.AppendFormat(" {0} {1}", NameSwitch, "MyProduct");
			builder.AppendFormat(" {0} {1}", FromVersionSwitch, "2011");
			builder.AppendFormat(" {0} {1}", FromVersionFolderSwitch, "C:\\API\\2011");
			builder.AppendFormat(" {0} {1}", ToVersionSwitch, "2012");
			builder.AppendFormat(" {0} {1}", ToVersionFolderSwitch, "C:\\API\\2012");
			builder.AppendFormat(" {0} {1}", OutputFolderSwitch, "C:\\API\\Results");
			builder.AppendLine();

			return builder.ToString();
		}

		/// <summary>
		/// Parses command line arguments into member variables for use by main program.
		/// </summary>
		/// <param name="args">List of arguments from console.</param>
		public void Parse(string[] args)
		{
			if (args.Length == 0)
			{
				throw new CommandLineException("No command line arguments found.");
			}

            const string forwardSlash = "/";
            const string optionOn = "+";
            const string optionOff = "-";

			Queue<string> argumentQueue = new Queue<string>(args.Length);

			foreach (var argument in args)
			{
                if (argument.StartsWith(forwardSlash))
				{
					argumentQueue.Enqueue(argument.ToLower());
				}
				else
				{
					argumentQueue.Enqueue(argument);
				}
			}

			string candidateProjectFile = argumentQueue.Peek();

			if (candidateProjectFile.EndsWith(FileConstants.ProjectFileExtension))
			{
				if (File.Exists(candidateProjectFile))
				{
					this.ProjectFile = candidateProjectFile;
				}
				else
				{
					throw new CommandLineException(string.Format("Project file {0} does not exist", candidateProjectFile));
				}
			}
			else
			{
				while (argumentQueue.Count > 0)
				{
					string optionName = argumentQueue.Dequeue();
					string value = string.Empty;

                    if (argumentQueue.Count > 0)
                    {
                        string nextArg = argumentQueue.Peek();

                        if (!nextArg.StartsWith(forwardSlash) && !nextArg.StartsWith(optionOn) && !nextArg.StartsWith(optionOff))
                        {
                            value = argumentQueue.Dequeue();
                        }
                    }

                    if (optionName.StartsWith(optionOn) || optionName.StartsWith(optionOff))
                    {
                        optionName = optionName.ToLower();
                    }

					switch (optionName)
					{
						case NameSwitch:

							this.ProductName = value;
							break;

						case FromVersionSwitch:

							this.FromVersion = value;
							break;

						case FromVersionFolderSwitch:

							this.FromVersionFolder = value;
							break;

						case ToVersionSwitch:

							this.ToVersion = value;
							break;

						case ToVersionFolderSwitch:

							this.ToVersionFolder = value;
							break;

						case OutputFolderSwitch:

							this.OutputFolder = value;
							break;

						case HtmlSwitch:

							this.ReportFormat = ReportFormat.Html;
							break;

                        case LegacyHtmlSwitch:

                            this.ReportFormat = ReportFormat.Html4;
                            break;

                        case MarkdownSwitch:

                            this.ReportFormat = ReportFormat.Md;
                            break;

						case MamlSwitch:

							this.ReportFormat = ReportFormat.Maml;
							break;

						case XmlSwitch:

							this.ReportFormat = ReportFormat.Xml;
							break;

						case SandcastleSwitch:

							this.SandcastleProject = value;
							break;

						case TopicIdSwitch:

							this.TopicId = value;
							break;

						case QuietSwitch:

							this.Quiet = true;
							break;

						case CleanSwitch:

							this.Clean = true;
							break;

                        case AdditionsOn:

                            this.ReportOnAdditions = true;
                            break;

                        case AdditionsOff:

                            this.ReportOnAdditions = false;
                            break;

                        case ChangesOn:

                            this.ReportOnChanges = true;
                            break;

                        case ChangesOff:

                            this.ReportOnChanges = false;
                            break;

                        case RemovalsOn:

                            this.ReportOnRemovals = true;
                            break;

                        case RemovalsOff:

                            this.ReportOnRemovals = false;
                            break;

                        case ObsoleteOn:

                            this.ReportOnObsolete = true;
                            break;

                        case ObsoleteOff:

                            this.ReportOnObsolete = false;
                            break;

                        default:

							throw new CommandLineException("unknown option \'" + optionName + "\'");
					}
				}
			}
		}
	}
}
